package org.concepts.java.core.creation.builder;

/**
 * <p>
 * Example of "Builder" pattern introduced in "Effective Java" book by Joshua
 * Block.
 * </p>
 * 
 * <code>
 *   Person president = new Person.Builder("Barack","Obama").age(48).build();
 * </code>
 * 
 * <p>
 * In above example, we are creating {@link Builder} object with the required
 * first and last name. After that we specify age (which is optional). Since
 * {@link Builder#age} returns {@link Builder}, we can specify n number of
 * optional parameters. Finally, we call {@link Builder#build()} to create the
 * person object. See additional examples in {@link TestPerson} class and
 * {@link Builder} javadoc.
 * </p>
 * 
 * </p> This pattern provides the following benefits:
 * <p>
 * </p>
 * <ol>
 * <li>
 * Lets us</li>
 * </ol>
 * 
 * @author vvvv
 * 
 */
public class Person {

  public static enum Sex {
    MALE, FEMALE, UNSPECIFIED
  };

  private String firstName;
  private String lastName;
  private int age;
  private Sex sex;

  /**
   * NOTE: We have made the constructor protected as we allow subclassing. See
   * {@link TestPerson} for example.
   * 
   * @param builder Builder to build person. Cannot be null.
   */
  protected Person(Builder builder) {
    this.firstName = builder.firstName;
    this.lastName = builder.lastName;

    this.age = builder.age;
    this.sex = builder.sex;
  }

  /**
   * Used to Create Person. Once person is created it's immutable. Person is
   * created using calls like:
   * <p/>
   * 
   * <code>
   *   Person president = new Person.Builder("Barack","Obama").build();
   * </code>
   * 
   * <p>
   * In above, the president object is created with the required first and last
   * name. The {@link #build()} method creates the Person object.
   * </p>
   * 
   * <code>
   *   Person president = new Person.Builder("Barack","Obama").age(48).build();
   * </code>
   * 
   * <p>
   * In addition to the previous example, the user is now able to specify
   * optional age for the person.
   * </p>
   * 
   * <code>
   *   Person president = new Person.Builder("Barack","Obama").age(48)
   *                                                          .sex(Sex.MALE)
   *                                                          .build();
   * </code>
   * 
   * <p>
   * In addition to the previous example, the user is now able to specify
   * chain all optional parameters (age, sex), as all the optional
   * builder methods return the builder object.
   * </p>
   * 
   * @author vvvv
   * 
   */
  public static class Builder {

    // Required params
    private String firstName;
    private String lastName;

    // Optional parameters with default.
    private int age = -1;
    private Sex sex = Sex.UNSPECIFIED;

    /**
     * Creates the builder with the parameters that are required to create a
     * valid person object.
     * 
     * @param firstName
     *          First name of the person. Cannot be null or empty.
     * @param lastName
     *          Last name of the person. Cannot be null or empty.
     */
    public Builder(String firstName, String lastName) {
      if (firstName == null || firstName.trim().length() == 0) {
        throw new IllegalArgumentException("First name cannot be null or empty");
      }

      if (lastName == null || lastName.trim().length() == 0) {
        throw new IllegalArgumentException("Last name cannot be null or empty");
      }

      this.firstName = firstName;
      this.lastName = lastName;
    }

    /**
     * Sets the person's age in the builder.
     * 
     * @param age
     *          age. Cannot be 0.
     * @return Instance of the builder.
     */
    public Builder age(int age) {
      if (age == 0) {
        throw new IllegalArgumentException(
            "Age 0 is not allowed. Please specify a valid age");
      }
      this.age = age;
      return this;
    }

    /**
     * Sets the person's sex.
     * 
     * @param sex
     *          sex. Can be null.
     * @return Instance of the builder
     */
    public Builder sex(Sex sex) {
      if (sex == null) {
        this.sex = Sex.UNSPECIFIED;
      } else {
        this.sex = sex;

      }
      return this;
    }

    /**
     * Builds the person object
     * 
     * @return
     */
    public Person build() {
      return new Person(this);
    }

  }

  /**
   * Human readable output of Person object. Please note that the format of the
   * output can change in future versions.
   */
  @Override
  public String toString() {
    return this.firstName + " " + this.lastName + ", age: " + age + ", sex: "
        + getSexAsString();
  }

  /**
   * Returns String representation of the Persons sex.
   * 
   * @return
   */
  public String getSexAsString() {
    switch (this.sex) {
    case MALE:
      return "male";
    case FEMALE:
      return "female";
    case UNSPECIFIED:
      return "unspecified";
    default:
      throw new IllegalStateException("Illegal state. Sex is not valid");
    }
  }

  /**
   * First name. Cannot be null or empty.
   * 
   * @return
   */
  public String getFirstName() {
    return firstName;
  }

  /**
   * Last name. Cannot be null or empty.
   * 
   * @return
   */
  public String getLastName() {
    return lastName;
  }

  /**
   * Age of the person. -1 indicates that the age was not specified by the user.
   * Also the age can never be 0.
   * 
   * @return
   */
  public int getAge() {
    return age;
  }

  /**
   * Sex of the person. It defaults to {@link Sex#UNSPECIFIED}.
   * 
   * @return
   */
  public Sex getSex() {
    return sex;
  }

}
